#! /usr/bin/env python
"""Script to generate a Python exe which calls into a main module.

The generated script calls the "main" function in the main module.
Since the import paths and environment variables are setup correctly
before calling the "main" function, the script can be run like any
other executable, without having to specify PYTHONPATH and other
environment variables explicitly.

Usage:

$> pyexe.py [-p PATH] [-v VAR=VALUE] -m MAIN_MODULE -x EXE

PATH is the optional path to prepend to sys.path before calling the
"main" function of the MAIN_MODULE. EXE is the path to the generated
executable. VAR is the optional environment variable that should be
set to VALUE before calling the "main" function.
"""

import argparse
import os
import stat
import sys

EXE_TEMPLATE = """#! /usr/bin/env python
# DO NOT EDIT.
# Executable generated by {script}

import os
import sys

def add_paths(paths: str):
    sys.path = paths + sys.path

    python_path_key = 'PYTHONPATH'
    old_value = os.environ.get(python_path_key, '')
    paths_string = os.pathsep.join(paths)
    new_value = paths_string if not old_value else paths_string + os.pathsep + old_value
    os.environ[python_path_key] = new_value


{path_adjustment}

{env_adjustment}

{import_stmt}

sys.exit({modname}.main())
"""


def parse_args():
  parser = argparse.ArgumentParser()
  parser.add_argument(
      "--path",
      "-p",
      action="append",
      default=[],
      help="Path to prepend to sys.path.",
  )
  parser.add_argument(
      "--env",
      "-v",
      action="append",
      default=[],
      help="Environment variable to set. Should be of the form VAR=VALUE.",
  )
  parser.add_argument(
      "--main_module",
      "-m",
      required=True,
      help="The fully qualified name of the target main module of the exe.",
  )
  parser.add_argument(
      "--exe_path", "-x", required=True, help="Path to the Python executable."
  )
  args = parser.parse_args()
  for env in args.env:
    if "=" not in env:
      sys.exit(
          "Environment variables should be specified in the form VAR=VALUE."
      )
  return args


def main():
  options = parse_args()

  path_adjustment = "add_paths([%s])" % ",".join(
      ["'%s'" % path for path in options.path]
  )

  env_adjustment = ""
  for env in options.env:
    var, value = env.split("=")
    env_adjustment += f"os.environ['{var}'] = '{value}'\n"

  if "." in options.main_module:
    pkg, modname = options.main_module.rsplit(".", 1)
    import_stmt = f"from {pkg} import {modname}"
  else:
    modname = options.main_module
    import_stmt = f"import {options.main_module}"

  with open(options.exe_path, "w") as target:
    text = EXE_TEMPLATE.format(
        script=__file__,
        path_adjustment=path_adjustment,
        env_adjustment=env_adjustment,
        import_stmt=import_stmt,
        modname=modname,
    )
    target.write(text)
  os.chmod(options.exe_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)


if __name__ == "__main__":
  main()
